package sketch.specs;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;

import sketch.ast.ASTUtils;
import sketch.specs.annotation.TestSketch;
import sketch.util.Checker;

public class SketchedMethodsCreator {
	
	//take a compilation unit as input, and create a list
	//of <code>SketchedMethodDeclaration</code>.
	public final String sourcePath;
	public final CompilationUnit unit;
	
	public final Map<Comment, SketchedBlock> sketchedBlocks;
	
	//the parsing results
//	private final Map<Comment, Block> commentBlocks;
//	private final Map<Block, Comment> blockComments;
	
	public SketchedMethodsCreator(String sourcePath) {
		Checker.checkNull(sourcePath, "The sourcePath input could not be null!");
		this.sourcePath = sourcePath;
		this.unit = ASTUtils.astRoot(sourcePath);
		//fetch the comment, block list
		BlockMappingVisitor blockVisitor = new BlockMappingVisitor(unit.getCommentList()); 
		blockVisitor.visit(unit);
		 Map<Comment, Block> commentBlocks = blockVisitor.getCommentMappings();
		 Map<Block, Comment> blockComments = blockVisitor.getBlockMappings();
		//convert the comment into string
		FileContents.addFile(sourcePath);
		sketchedBlocks = new LinkedHashMap<Comment, SketchedBlock>();
		for(Comment comment : commentBlocks.keySet()) {
			Block block = commentBlocks.get(comment);
			String commentStr = FileContents.fetchCommentString(this.sourcePath, comment, this.unit);
			SketchedBlock sketchBlock = this.createSketchedBlock(commentStr, block);
			if(sketchBlock != null) {
			    this.sketchedBlocks.put(comment, sketchBlock);
			}
		}
	}
	
	public CompilationUnit getUnit() {
		return this.unit;
	}
	
	/**
	 * The client should call createSketchedMethodList() first to establish 
	 * **/
	public Map<Comment, SketchedBlock> getSketchedBlocks() {
		return this.sketchedBlocks;
	}
	public Collection<SketchedBlock> getSketchedBlockList() {
		return this.sketchedBlocks.values();
	}
	
	public List<SketchedMethodDeclaration> createSketchedMethodList() {
		List<SketchedMethodDeclaration> sketchedMethods = new LinkedList<SketchedMethodDeclaration>();
		//create each sketched method declaration one by one
		List<MethodDeclaration> methodDeclarations = ASTUtils.getMethodsWithAnnotation(this.unit, TestSketch.class.getSimpleName());
		for(MethodDeclaration methodDeclaration : methodDeclarations) {
			SketchedMethodDeclaration sketechedMethod = new SketchedMethodDeclaration(methodDeclaration);
			sketchedMethods.add(sketechedMethod);
		}
		//processing the list, and initialize the body of each SketchedMethodDeclaration
		//establish the parent relations
		this.processSketchedMethodList(sketchedMethods);
		return sketchedMethods;
	}
	
	private void processSketchedMethodList(List<SketchedMethodDeclaration> sketchedMethodList) {
		Collection<SketchedBlock> flatBlocks = this.sketchedBlocks.values();
		//check the correctness, a block can not belong to multiple method declaration
		Set<SketchedBlock> alreadyProcessed = new LinkedHashSet<SketchedBlock>();
		for(SketchedMethodDeclaration sketchedMethod : sketchedMethodList) {
			for(SketchedBlock sketchedBlock : flatBlocks) {
				Checker.checkNull(sketchedBlock, "Sketch block could not be null!");
				if(ASTUtils.isNested(sketchedBlock.realBlock, sketchedMethod.method_declaration)) {
					//add
					sketchedMethod.addSubBlock(sketchedBlock);
					Checker.checkTrue(!alreadyProcessed.contains(sketchedBlock), "The skecthed block has already been processed!");
					alreadyProcessed.add(sketchedBlock);
				}
			}
		}
		//create the inner construct of the sketched method declaration
		for(SketchedMethodDeclaration sketchedMethod : sketchedMethodList) {
			sketchedMethod.buildSubBlockHierarchy();
		}
		//establish the parent relations
		for(SketchedMethodDeclaration sketchedMethod : sketchedMethodList) {
			sketchedMethod.buildParentRelations();
		}
	}
	
	private SketchedBlock createSketchedBlock(String commentStr, Block block) {
		if(commentStr.indexOf(SketchedRepeatBlock.COMMENT_REPRESENTATION) != -1) {
			return new SketchedRepeatBlock(block);
		} else if (commentStr.indexOf((SketchedChooseOneBlock.COMMENT_REPRESENTATION)) != -1) {
			return new SketchedChooseOneBlock(block);
		} else if (commentStr.indexOf(SketchedChooseAllBlock.COMMENT_REPRESENTATION) != -1
				|| commentStr.indexOf(SketchedChooseAllBlock.COMMENT_REPRESENTATION_ALIAS) != -1) {
			return new SketchedChooseAllBlock(block);
		} else if (commentStr.indexOf(SketchedExhaustBlock.COMMENT_REPRESENTATION) != -1) {
			return new SketchedExhaustBlock(block);
		} else if (commentStr.indexOf(SketchedTryCatchBlock.COMMENT_REPRESENTATION) != -1) {
			return new SketchedTryCatchBlock(block);
		} else if (commentStr.indexOf(SketchedIdentifierBlock.COMMENT_REPRESENTATION) != -1) {
			int startIndex = commentStr.indexOf(SketchedIdentifierBlock.COMMENT_REPRESENTATION);
			int leftParenth = commentStr.indexOf("(", startIndex);
			int rightParenth = commentStr.indexOf(")", startIndex);
			if(leftParenth == -1 || rightParenth == -1 || rightParenth == leftParenth + 1) {
				return null;
			} else {
				String identifier = commentStr.substring(leftParenth + 1, rightParenth).trim();
				if(identifier.equals("")) {
					return null;
				} else {
					return new SketchedIdentifierBlock(block, identifier);
				}
			}
		}else {
			return null;
		}
	}
}